#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QFileDialog>

#include "gui/main_widget.h"
#include "gui/menu_manager.h"
#include "gui/body.h"

// features
#include "gui/features/feature.h"
#include "gui/features/file_dialog_feature.h"
#include "gui/features/exit_feature.h"
#include "gui/features/player_feature.h"
#include "gui/features/edit_song_feature.h"
#include "gui/features/recent_docs_feature.h"
#include "gui/features/under_construction_feature.h"

class Controller: public MenuManagerListener, public BodyListener, public ControllerProxy
{
public:
    Controller(Engine* _engine, MainWidget* _mw):engine(_engine), mw(_mw)
    {
        // menu control
        manager = new MenuManager(engine, mw);
        manager->AddObserver(this);
        manager->SetVisible(true);
        mainSelected = 0;
        submenuSelected = 0;
        mainToOpen = 0;
        submenuToAttachTo = 0;
        currentFeature = 0;
        postAction = -1;
        postOpenAction = -1;
        attachedToExit = false;
        body = new Body(engine, 150, 15, mw->width() - 15, mw->height() - 15);
        body->AddObserver(this);
        mw->AddGui(body);
        Init();
    }

    ~Controller()
    {
        manager->SetVisible(false);
        delete manager;
    }

    /* ------------------ application initialization ------------------------------------------- */
    void Init()
    {
        DisableMenus();
        BuildFeatures();
    }

    void DisableMenus()
    {
        vector< pair< int, int > > toDisable;
        toDisable.push_back( pair<int, int>(0, 1) );   // song -> save
        toDisable.push_back( pair<int, int>(0, 2) );   // song -> save as
        toDisable.push_back( pair<int, int>(0, 3) );   // song -> player
        toDisable.push_back( pair<int, int>(0, 4) );   // song -> edit
        toDisable.push_back( pair<int, int>(1, 2) );   // playlist -> save
        toDisable.push_back( pair<int, int>(1, 3) );   // playlist -> save as
        toDisable.push_back( pair<int, int>(1, 4) );   // playlist -> edit
        toDisable.push_back( pair<int, int>(1, 5) );   // playlist -> close
        toDisable.push_back( pair<int, int>(2, 0) );   // stats -> session stats
        toDisable.push_back( pair<int, int>(2, 1) );   // stats -> song stats
        toDisable.push_back( pair<int, int>(2, 2) );   // stats -> playlist stats

        for(vector< pair<int, int> >::iterator i=toDisable.begin(); i!=toDisable.end(); ++i)
        {
            manager->submenus[(*i).first][(*i).second]->SetEnabled(false);
        }
    }

    void BuildFeatures()
    {
        Feature* f = new ExitFeature(ControllerProxy::EXIT_FEATURE, engine, 160, 25, mw->width() - 25 - 160, mw->height() - 50, this, mw);
        features[ControllerProxy::EXIT_FEATURE] = f;

        f = new OpenFileDialogFeature(ControllerProxy::OPEN_FILE_DIALOG_FEATURE, engine, 160, 25, mw->width() - 25 - 160, mw->height() - 50, this, mw);
        features[ControllerProxy::OPEN_FILE_DIALOG_FEATURE] = f;

        f = new SaveFileDialogFeature(ControllerProxy::SAVE_FILE_DIALOG_FEATURE, engine, 160, 25, mw->width() - 25 - 160, mw->height() - 50, this, mw);
        features[ControllerProxy::SAVE_FILE_DIALOG_FEATURE] = f;

        f = new PlayerFeature(ControllerProxy::PLAYER_FEATURE, engine, 160, 25, mw->width() - 25 - 260, mw->height() - 50, this, mw);
        features[ControllerProxy::PLAYER_FEATURE] = f;

        f = new EditSongFeature(ControllerProxy::EDIT_SONG_FEATURE, engine, 160, 25, mw->width() - 25 - 260, mw->height() - 50, this, mw);
        features[ControllerProxy::EDIT_SONG_FEATURE] = f;

        f = new RecentDocsFeature( ControllerProxy::RECENT_DOCS_FEATURE, engine, 160, 25, mw->width() - 25 - 260, mw->height() - 50, this, mw );
        features[ ControllerProxy::RECENT_DOCS_FEATURE ] = f;

        f = new UnderConstructionFeature( ControllerProxy::RECENT_DOCS_FEATURE, engine, 160, 25, mw->width() - 25 - 260, mw->height() - 50, this, mw );
        features[ ControllerProxy::UNDER_CONSTRUCTION_FEATURE ] = f;
    }
    /* ----------------------------------------------------------------------------------------- */

    /* ---------------------- ControllerProxy implementation ----------------------------------- */
    virtual void UnloadRequest(int nextFeatureId)
    {
        if(currentFeature->GetId() == ControllerProxy::OPEN_FILE_DIALOG_FEATURE)
        {
            if(nextFeatureId == ControllerProxy::EDIT_SONG_FEATURE)
            {   // the current main menu opened is Song
                submenuToAttachTo = manager->submenus[0][4];
            }
            else if( nextFeatureId == ControllerProxy::PLAYER_FEATURE )
            {
                submenuToAttachTo = manager->submenus[0][3];
            }
            submenuToAttachTo->SetSelected(true);
            body->Detach();
        }
        else if( currentFeature->GetId() == ControllerProxy::RECENT_DOCS_FEATURE )
        {
            if( nextFeatureId == ControllerProxy::EDIT_SONG_FEATURE )
            {
                submenuToAttachTo = manager->submenus[0][4];
            }
            else if( nextFeatureId == ControllerProxy::PLAYER_FEATURE )
            {
                submenuToAttachTo = manager->submenus[0][3];
            }
            submenuToAttachTo->SetSelected(true);
            body->Detach();
        }
        else if(currentFeature->GetId() == ControllerProxy::EDIT_SONG_FEATURE)
        {
            if(nextFeatureId == ControllerProxy::SAVE_FILE_DIALOG_FEATURE)
            {   // we must save the song
                submenuToAttachTo = manager->submenus[0][1];
                submenuToAttachTo->SetSelected(true);
                body->Detach();
            }
        }else if(currentFeature->GetId() == ControllerProxy::SAVE_FILE_DIALOG_FEATURE)
        {
            if(saveSong)
            {
                saveSong = false;
                if(prevFeatureId == ControllerProxy::EDIT_SONG_FEATURE)
                {
                    submenuToAttachTo = manager->submenus[0][4];
                }
                else if( prevFeatureId == ControllerProxy::PLAYER_FEATURE )
                {
                    submenuToAttachTo = manager->submenus[0][3];
                }
                submenuToAttachTo->SetSelected(true);
                body->Detach();
            }
        }
        else if( currentFeature->GetId() == ControllerProxy::PLAYER_FEATURE )
        {
            if( nextFeatureId == ControllerProxy::EDIT_SONG_FEATURE )
            {
                submenuToAttachTo = manager->submenus[0][4];
                submenuToAttachTo->SetSelected( true );
                body->Detach();
            }
            else if( nextFeatureId == ControllerProxy::SAVE_FILE_DIALOG_FEATURE )
            {
                submenuToAttachTo = manager->submenus[0][1];
                submenuToAttachTo->SetSelected(true);
                body->Detach();
            }
        }
    }
    /* ----------------------------------------------------------------------------------------- */

    /* ----------------------- MenuManagerListener implementation ------------------------------ */
    virtual void OnMenuOpened(MenuManager* mm, Menu* m){};
    virtual void OnMenuClosed(MenuManager* mm, Menu* m)
    {
        if(mainToOpen != 0)
        {
            mainSelected = mainToOpen;
            mm->OpenMenu(mainSelected->GetId());
            mainToOpen = 0;
        }
    };

    virtual void OnMenuClick(MenuManager* mm, Menu* m, QMouseEvent* e)
    {
        Feature* nextFeature = GetFeature(m);

        if(m->IsSubmenu() && m->GetText() != "Exit")
        {   // submenu click
            if(submenuSelected == 0)
            {   // no submenu selected
                if(!body->IsVisible())
                {
                    submenuSelected = m;
                    currentFeature = nextFeature;
                    body->Show(m, nextFeature);
                }
            } else
            {   // another submenu already selected
                if(submenuSelected->GetId() == m->GetId())
                {   // this submenu has been clicked once again, so we must close the body
                    if(body->IsAttached())
                    {   // body attached: we must hide it
                        submenuSelected = 0;
                        body->Hide();
                    }
                } else
                {   // another submenu of the current main menu has been clicked
                    submenuToAttachTo = m;
                    body->Detach();
                }
            }
        }else if(!m->IsSubmenu())
        {   // main menu click
            if(mainSelected == 0)
            {
                if(attachedToExit)
                {
                    postAction = 3;
                    manager = mm;
                    current = m;
                    body->Hide();
                } else
                {
                    mainSelected = m;
                    mm->OpenMenu(m->GetId());
                }
            } else
            {   // there is already one main menu selected
                // in every situation, we first close other opened  attached submenus
                if(submenuSelected != 0)
                {
                    postAction = 1;
                    manager = mm;
                    current = m;
                    //if(body->IsAttached())
                    //{
                    body->Hide();
                    return;
                    //}
                }else
                {   // wrong situation: a submenu is selected but the body is detached

                }
                CloseMain(mm, m);
            }
        } else
        {   // exit click
            if(submenuSelected == 0)
            {   // no submenus selected
                submenuSelected = m;
                if(body->IsVisible())
                {
                    postAction = 2;
                    submenuToAttachTo = m;
                    body->Detach();
                }else
                {
                    postAction = 2;
                    currentFeature = nextFeature;
                    body->Show(m, nextFeature);
                }
            } else
            {
                if(submenuSelected->GetId() ==  5)
                {   // exit re-clicked (hiding body)
                    body->Hide();
                }else
                {   // another submenu is selected
                    //if(body->IsAttached())
                    //{
                        submenuToAttachTo = m;
                        body->Detach();
                    //}else
                    //{   /* wrong situation */ }
                }
            }
        }
    };
    /* ----------------------------------------------------------------------------------------- */

    /* --------------------- BodyListener implementation --------------------------------------- */
    virtual void OnBodyAttach(Menu* m)
    {
        m->SetSelected(true);

        if(postAction == 2)
        {
            attachedToExit = true;
            exitMenu = m;
            postAction = -1;
        }else
        {
            attachedToExit = false;
        }
    };

    virtual void OnBodyDetach(Menu* m)
    {
        attachedToExit = false;
        if(submenuToAttachTo != 0)
        {
            currentFeature = GetFeature(submenuToAttachTo);
            UpdateMenuStatus();
            m->SetSelected(false);
            if(submenuToAttachTo->GetId() == 5) postAction = 2;
            body->AttachTo(submenuToAttachTo, currentFeature);
            submenuSelected = submenuToAttachTo;
            submenuToAttachTo = 0;
        }
    };

    virtual void OnBodyHidden()
    {
        if(postAction == 1)
        {
            submenuSelected->SetSelected(false);
            CloseMain(manager, current);
            postAction = -1;
        } else if(postAction == 3)
        {
            exitMenu->SetSelected(false);
            mainSelected = current;
            manager->OpenMenu(current->GetId());
        }
        currentFeature = 0;
        submenuSelected = 0;
        attachedToExit = false;
    };
    /* ----------------------------------------------------------------------------------------- */


private:
    /* -------------------------------- fields ------------------------------------------------- */
    // other fields
    Engine* engine;
    MainWidget* mw;
    Body* body;

    // menu control
    MenuManager* manager;
    Menu* mainSelected;
    Menu* mainToOpen;
    Menu* submenuSelected;
    Menu* submenuToAttachTo;
    Menu* exitMenu;
    int postAction;
    Menu* current;
    bool attachedToExit;

    // feature control
    map< int, Feature* > features;
    Feature* currentFeature;
    QFileDialog* fd;
    /* ----------------------------------------------------------------------------------------- */

    /* -------------------------- private functions -------------------------------------------- */
    void CloseMain(MenuManager* mm, Menu* m)
    {
        if(mainSelected->GetId() == m->GetId())
        {
            mm->CloseMenu();
            mainSelected = 0;
        }else
        {
            mm->CloseMenu();
            mainToOpen = m;
        }
    }

    Feature* GetFeature(Menu* clicked)
    {
        Feature* f = 0;
        if(clicked->IsSubmenu())
        {
            int mId = clicked->GetId();
            if(mId == MenuManager::EXIT)
            {
                f = features[ControllerProxy::EXIT_FEATURE];
            }
            else if(mId == MenuManager::OPEN_SONG)
            {
                OpenFileDialogFeature* ofdf = (OpenFileDialogFeature*)features[ControllerProxy::OPEN_FILE_DIALOG_FEATURE];
                QStringList filters;
                filters << "*.mp3" << "*.wav" << "*.ogg" << "*.mps";
                ofdf->SetNameFilters(filters);
                openSong = true;
                f = ofdf;
            }
            else if (mId == MenuManager::OPEN_PLAYLIST)
            {
                OpenFileDialogFeature* ofdf = (OpenFileDialogFeature*)features[ControllerProxy::OPEN_FILE_DIALOG_FEATURE];
                QStringList filters;
                filters << "m3u";
                ofdf->SetNameFilters(filters);
                openSong = false;
                f = ofdf;
            }
            else if(mId == MenuManager::SAVE_SONG ||
                    mId == MenuManager::SAVE_AS_SONG ||
                    mId == MenuManager::SAVE_PLAYLIST ||
                    mId == MenuManager::SAVE_AS_PLAYLIST)
            {
                saveSong = (mId == MenuManager::SAVE_SONG || mId == MenuManager::SAVE_AS_SONG);
                f = features[ControllerProxy::SAVE_FILE_DIALOG_FEATURE];
            }
            else if(mId == MenuManager::EDIT_SONG)
            {
                f = features[ControllerProxy::EDIT_SONG_FEATURE];
            }
            else if( mId == MenuManager::RECENT_SONG ||
                     mId == MenuManager::RECENT_PLAYLIST )
            {
                postOpenAction = ( mId == MenuManager::RECENT_SONG )? 1 : 0;
                f = features[ ControllerProxy::RECENT_DOCS_FEATURE ];
            }
            else if( mId == MenuManager::PLAYER )
            {
                f = features[ ControllerProxy::PLAYER_FEATURE ];
            }else
            {
                f = features[ ControllerProxy::UNDER_CONSTRUCTION_FEATURE ];
            }
        }
        return f;
    }

    void UpdateMenuStatus()
    {
        vector< pair< int, int > > toEnable;

        if(currentFeature->GetId() == ControllerProxy::EDIT_SONG_FEATURE ||
           currentFeature->GetId() == ControllerProxy::PLAYER_FEATURE )
        {
            toEnable.push_back( pair<int, int>(0, 1) );   // song -> save
            toEnable.push_back( pair<int, int>(0, 2) );   // song -> save as
            toEnable.push_back( pair<int, int>(0, 3) );   // song -> player
            toEnable.push_back( pair<int, int>(0, 4) );   // song -> edit

            for(vector< pair<int, int> >::iterator i=toEnable.begin(); i!=toEnable.end(); ++i)
            {
                manager->submenus[(*i).first][(*i).second]->SetEnabled(true);
            }
        }
    }

    bool IsInTheSameSubmenu(int featureToLoad)
    {
        if(featureToLoad == ControllerProxy::EDIT_SONG_FEATURE &&
           mainSelected->GetId() == MenuManager::SONG)
        {
            return true;
        }
        return false;
    }
    /* ----------------------------------------------------------------------------------------- */
};

#endif // CONTROLLER_H
